﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Momentum.Static;
using Momentum.Types;

namespace Momentum
{

    /// <summary>
    /// The main class that starts everything.
    /// </summary>
    public class Momentum : Microsoft.Xna.Framework.Game
    {

        #region Fields

        Color backgroundColor = Color.Black;
        
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font;
        Texture2D title;

        mWorld world;
        mCamera2D camera;
        mMouse mouse;
        Vector2 mousePosition;

        KeyboardState currentKeyboardState;
        MouseState currentMouseState;
        GamePadState currentGamePadState;

        mNetwork mNetworkSession;
        mExplosionParticleSystem explosion;

        int keyDelay = 70;
        int actual_keyDelay = 0;

        string errorMessage = null;

        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        public mExplosionParticleSystem Explosion
        {
            get { return explosion; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor.
        /// </summary>
        public Momentum()
        {
            mStaticWorld.ScreenSize = new Vector2(1024, 768);
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = (int)mStaticWorld.ScreenSize.X;
            graphics.PreferredBackBufferHeight = (int)mStaticWorld.ScreenSize.Y;
            graphics.IsFullScreen = false;
            Content.RootDirectory = "Content";
            Components.Add(new GamerServicesComponent(this));
            explosion = new mExplosionParticleSystem(this, 2);
            Components.Add(explosion);
            IsMouseVisible = false;
            world = new mWorld();
            camera = new mCamera2D();
            mouse = new mMouse();
            mNetworkSession = new mNetwork(this, ref world,8,Content);
            //SignedInGamer.SignedIn += new EventHandler<SignedInEventArgs>(SignedInGamer_SignedIn);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// Loading the content.
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Interface/MainFont");
            title = Content.Load<Texture2D>("title");
            world.LoadContent(Content, graphics);
            camera.Position = mStaticWorld.Size / 2;
            camera.Rotation = 0f;
            camera.Zoom = 1f;
            camera.MoveUsingScreenAxis = true;
            mouse.LoadContent(GraphicsDevice, Content);
            mStaticWorld.TankFont = Content.Load<SpriteFont>("Interface/TankFont");
            mWorldLoader.LoadRandomWorld(Content, ref world);
            CameraChanged();
        }

        /*void SignedInGamer_SignedIn(object sender, SignedInEventArgs e)
        {
            //e.Gamer.Tag = new mPlayer();
            //mNetworkSession.GamerJoinedEventHandler();
            mTank tank = Content.Load<mTank>(@"Tanks/" + mStaticObject.TankNames[team]);
            tank.Position = new Vector2(rand.Next((int)mStaticWorld.Size.X), rand.Next((int)mStaticWorld.Size.Y));
            mWeapon weapon = Content.Load<mWeapon>(@"Weapons/" + mStaticObject.BulletNames[0]);
            weapon.Load(Content);
            e.Gamer.Tag = new mPlayer(tank, weapon, team);
            team++;
            team %= 2;
        }*/


        #endregion

        #region Update

        [DllImport("user32.dll")]
        static extern void ClipCursor(ref Rectangle rect);

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if(actual_keyDelay<keyDelay)
                actual_keyDelay++;
            Rectangle win = Window.ClientBounds;
            win.Width += win.X;
            win.Height += win.Y;
            ClipCursor(ref win);

            mouse.Update();

            HandleInput();

            CameraChanged();

            if (!mNetworkSession.HasStarted())
            {
                UpdateMenuScreen();
            }
            else
            {
                if(mNetworkSession.SessionState() == NetworkSessionState.Lobby){
                    UpdateLobby();
                } else {
                    // Read inputs for locally controlled tanks, and send them to the server.
                    foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers())
                    {
                        UpdateLocalGamer(gamer);
                    }
                    // If we are the server, update all the tanks and transmit
                    // their latest positions back out over the network.
                    if (mNetworkSession.IsHost())
                    {
                        UpdateServer(gameTime);
                    }
                }
                mNetworkSession.UpdateNetworkSession();
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// This function is called when the camera's values have changed
        /// and is used to update the properties of the tiles and animated sprite
        /// </summary>
        public void CameraChanged()
        {
            world.SetCamera(camera);
            camera.ResetChanged();
        }

        protected void UpdateLobby()
        {
            //tengo un pequeño delay
            if (actual_keyDelay < keyDelay)
                return;

            // The host checks if everyone is ready, and moves to game play if true.
            if (mNetworkSession.IsHost())
            {
                if (mNetworkSession.IsEveryoneReady())
                {
                    mNetworkSession.StartGame();
                    return;
                }
            }
            // Signal I'm ready to play!
            if (IsPressed(Keys.A, Buttons.A))
            {
                actual_keyDelay -= keyDelay;
                foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers())
                    gamer.IsReady = true;
            }

            if(IsPressed(Keys.T,Buttons.DPadLeft))
            {
                actual_keyDelay -= keyDelay;
                foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers())
                {
                    mPlayer player = gamer.Tag as mPlayer;
                    player.ChangeTankType(Content);
                }
            }

            if (IsPressed(Keys.C, Buttons.DPadRight))
            {
                actual_keyDelay -= keyDelay;
                foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers())
                {
                    mPlayer player = gamer.Tag as mPlayer;
                    player.ChangeTeam();
                }
            }
        }

        /// <summary>
        /// Menu screen provides options to create or join network sessions.
        /// </summary>
        void UpdateMenuScreen()
        {
            if (IsActive)
            {
                if (Gamer.SignedInGamers.Count == 0)
                {
                    Guide.ShowSignIn(1, false);
                }
                else if (IsPressed(Keys.A, Buttons.A))
                {
                    actual_keyDelay -= keyDelay;
                    CreateSession();
                }
                else if (IsPressed(Keys.B, Buttons.B))
                {
                    actual_keyDelay -= keyDelay;
                    JoinSession();
                }
            }
        }

        /// <summary>
        /// Metodo principal que calcula todo y luego envia los resultados a los clientes
        /// </summary>
        void UpdateServer(GameTime gameTime)
        {
            for (int i = 0; i < world.Objects.Count; i++)
            {
                for (int j = i + 1; j < world.Objects.Count; j++)
                {
                    mObject.Collide(world.Objects[i], world.Objects[j]);
                    if (world.Objects[i].HP <= 0)
                    {
                        world.Objects.RemoveAt(i);
                    }
                    if (world.Objects[j].HP <= 0)
                    {
                        world.Objects.RemoveAt(j);
                    }
                }
            }
            for (int i = 0; i < mNetworkSession.AllGamers().Count; i++)
            {
                mPlayer player1 = mNetworkSession.AllGamers()[i].Tag as mPlayer;
                for (int j = 0; j < player1.weapon.Bullets.Count; j++)
                {
                    mObject.Collide(player1.tank, player1.weapon.Bullets[j]);
                    for (int k = 0; k < world.Objects.Count; k++)
                    {
                        mObject.Collide(player1.weapon.Bullets[j], world.Objects[k]);
                        if (world.Objects[k].HP <= 0)
                        {
                            world.Objects.RemoveAt(k);
                        }
                    }
                    if (player1.weapon.Bullets[j].HP <= 0)
                    {
                        world.Explosions.Add(player1.weapon.Bullets[j].Position);
                        player1.weapon.Bullets.RemoveAt(j);
                    }
                }
                for (int j = 0; j < world.Objects.Count; j++)
                {
                    mObject.Collide(player1.tank, world.Objects[j]);
                    if (world.Objects[j].HP <= 0)
                    {
                        world.Objects.RemoveAt(j);
                    }
                }
                for (int j = i; j < mNetworkSession.AllGamers().Count; j++)
                {
                    mPlayer player2 = mNetworkSession.AllGamers()[j].Tag as mPlayer;
                    if (player1 != player2)
                    {
                        mObject.Collide(player1.tank, player2.tank);
                        for (int k = 0; k < player1.weapon.Bullets.Count; k++)
                        {
                            mObject.Collide(player2.tank, player1.weapon.Bullets[k]);
                            if (player1.weapon.Bullets[k].HP <= 0)
                            {
                                world.Explosions.Add(player1.weapon.Bullets[k].Position);
                                player1.weapon.Bullets.RemoveAt(k);
                            }
                        }
                        for (int k = 0; k < player2.weapon.Bullets.Count; k++)
                        {
                            mObject.Collide(player1.tank, player2.weapon.Bullets[k]);
                            if (player2.weapon.Bullets[k].HP <= 0)
                            {
                                world.Explosions.Add(player2.weapon.Bullets[k].Position);
                                player2.weapon.Bullets.RemoveAt(k);
                            }
                        }
                    }
                    if (!player2.tank.IsAlive)
                    {
                        world.Explosions.Add(player2.tank.Position);
                    }
                }
                if (!player1.tank.IsAlive)
                {
                    world.Explosions.Add(player1.tank.Position);
                }
                UpdatePlayer(gameTime, player1);
                world.Update();
            }
            mNetworkSession.sendPLayerData();
            mNetworkSession.sendWorldData();
        }

        void UpdatePlayer(GameTime gameTime, mPlayer p)
        {
            if (p.tank.IsAlive)
            {
                p.CalculateFuture();
                if (obtainVictoriousTeam() == -1)
                {
                    p.Shoot(gameTime);
                }
            }
        }

        #endregion

        #region Comunication

        void CreateSession()
        {
            DrawClearMessage("Creando la sesion...");
            mNetworkSession.CreateSession();
            errorMessage = mNetworkSession.ErrorMessage();
        }

        void JoinSession()
        {
            DrawClearMessage("Buscando sesiones...");
            mNetworkSession.JoinSession();
            errorMessage = mNetworkSession.ErrorMessage();
        }

        #endregion

        #region Draw

        /// <summary>
        /// Retorna -1 si ningun team a ganado o el id del team que gano
        /// </summary>
        /// <returns></returns>
        protected int obtainVictoriousTeam()
        {
            int n_players_1 = 0;
            int n_players_2 = 0;
            int n_alive_1 = 0;
            int n_alive_2 = 0;
            foreach (NetworkGamer gamer in mNetworkSession.AllGamers())
            {
                mPlayer player = gamer.Tag as mPlayer;
                if (player.team == 0)
                {
                    n_players_1++;
                    if (player.Tank.IsAlive)
                        n_alive_1++;
                }
                else
                {
                    n_players_2++;
                    if (player.Tank.IsAlive)
                        n_alive_2++;
                }
            }
            if (n_players_2 > 0 && n_alive_2 == 0)
                return 0;
            if (n_players_1 > 0 && n_alive_1 == 0)
                return 1;
            return -1;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(backgroundColor);

            if (!mNetworkSession.HasStarted())
            {
                DrawMenuScreen();
            }
            else
            {
                if (mNetworkSession.SessionState() == NetworkSessionState.Lobby)
                {
                    DrawLobby();
                }
                else
                {
                    DrawNetworkSession();
                }
            }

            mouse.Draw();

            base.Draw(gameTime);
        }


        /// <summary>
        /// Draws the lobby
        /// </summary>
        private void DrawLobby()
        {
            GraphicsDevice.Clear(backgroundColor);

            spriteBatch.Begin();
            spriteBatch.Draw(this.title, new Vector2(mStaticWorld.ScreenSize.X / 2, 1.55f * (title.Height / 2)), null, Color.White, 0, new Vector2(title.Width / 2, title.Height / 2), 1.55f, SpriteEffects.None, 0);
            spriteBatch.End();

            DrawCenteredMessage("Lobby (A=ready, C=change team, T=change tank)", new Vector2(mStaticWorld.ScreenSize.X / 2, 450), Color.Gray);

            float y = 470;

            foreach (NetworkGamer gamer in mNetworkSession.AllGamers())
            {
                mPlayer player = gamer.Tag as mPlayer;
                string text = gamer.Gamertag;
                text += " / " + player.tank.Name;

                /*if (player.picture == null)
                {
                    GamerProfile gamerProfile = gamer.GetProfile();
                    player.picture = gamerProfile.GamerPicture;
                }*/
                float x = 100;
                if (player.team == 1)
                {
                    x = mStaticWorld.ScreenSize.X / 2;
                }
                if (gamer.IsReady)
                    text += " - ready!";

                //spriteBatch.Draw(player.picture, new Vector2(x+100, y), Color.White);
                //spriteBatch.DrawString(font, text, new Vector2(x, y), Color.White);
                DrawMessage(text, new Vector2(x, y), Color.Gray);

                y += font.LineSpacing + 10;
            }

        }


        /// <summary>
        /// Draws the startup screen used to create and join network sessions.
        /// </summary>
        void DrawMenuScreen()
        {
            spriteBatch.Begin();
            spriteBatch.Draw(this.title, new Vector2(mStaticWorld.ScreenSize.X / 2, 1.55f*(title.Height / 2)), null, Color.White, 0, new Vector2(title.Width / 2, title.Height / 2), 1.55f, SpriteEffects.None, 0);
            spriteBatch.End();
            DrawCenteredMessage("A = Crear sesion", new Vector2(mStaticWorld.ScreenSize.X / 2, 450), Color.Gray);
            DrawCenteredMessage("B = Unirse a una sesion", new Vector2(mStaticWorld.ScreenSize.X / 2, 480), Color.Gray);
            if (!string.IsNullOrEmpty(errorMessage))
            {
                DrawCenteredMessage("Error:\n" + errorMessage.Replace(". ", ".\n"), new Vector2(mStaticWorld.ScreenSize.X / 2, 600), Color.Gray);
            }
        }

        void DrawMessage(string message, Vector2 pos)
        {
            DrawMessage(message, pos, Color.Black);
        }

        void DrawMessage(string message, Vector2 pos, Color shadow)
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(font, message, new Vector2(pos.X + 1, pos.Y + 1), shadow);
            spriteBatch.DrawString(font, message, pos, Color.White);
            spriteBatch.End();
        }

        void DrawCenteredMessage(string message, Vector2 pos)
        {
            DrawCenteredMessage(message, pos, Color.Black);
        }

        void DrawCenteredMessage(string message, Vector2 pos, Color shadow)
        {
            Vector2 messageSize = font.MeasureString(message);
            spriteBatch.Begin();
            spriteBatch.DrawString(font, message, new Vector2(pos.X + 1, pos.Y + 1), shadow, 0, new Vector2(messageSize.X / 2, messageSize.Y / 2), 1f, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, message, pos, Color.White, 0, new Vector2(messageSize.X / 2, messageSize.Y / 2), 1f, SpriteEffects.None, 0);
            spriteBatch.End();
        }

        /// <summary>
        /// Helper draws notification messages before calling blocking network methods.
        /// </summary>
        void DrawClearMessage(string message)
        {
            if (!BeginDraw()) return;
            GraphicsDevice.Clear(backgroundColor);
            spriteBatch.Begin();
            spriteBatch.Draw(this.title, new Vector2(mStaticWorld.ScreenSize.X / 2, 1.55f * (title.Height / 2)), null, Color.White, 0, new Vector2(title.Width / 2, title.Height / 2), 1.55f, SpriteEffects.None, 0);
            spriteBatch.End();
            DrawCenteredMessage(message, new Vector2(mStaticWorld.ScreenSize.X / 2, 450), Color.Gray);
            EndDraw();
        }

        void DrawVictoryStatus()
        {
            foreach (NetworkGamer gamer in mNetworkSession.AllGamers())
            {
                if (gamer.IsLocal)
                {
                    mPlayer player = gamer.Tag as mPlayer;
                    // draw victory message if victory is archieved
                    int v = obtainVictoriousTeam();
                    if (v >= 0)
                    {
                        string message;
                        if (player.team == v)
                            message = "Victoria!";
                        else
                            message = "Has sido derrotado.";
                        DrawCenteredMessage(message, mStaticWorld.ScreenCenter, Color.Black);
                    }
                    break;
                }
            }
        }



        void DrawNetworkSession()
        {
            Vector2 mouseCenterPosition = mousePosition - mStaticWorld.ScreenCenter;
            Vector2 mouseAbsolutePosition = mouseCenterPosition;
            foreach (NetworkGamer gamer in mNetworkSession.AllGamers())
            {
                if (gamer.IsLocal)
                {
                    mPlayer player = gamer.Tag as mPlayer;
                    camera.Position = player.tank.Position + player.tank.MouseElasticity * mouseCenterPosition;
                    mouseAbsolutePosition += player.tank.Position;
                    world.SetCamera(camera);
                    world.DrawBackground(spriteBatch);
                    break;
                }
            }

            explosion.CameraPosition = camera.Position;
            for (int i = 0; i < world.Explosions.Count; i++)
            {
                explosion.AddParticles(world.Explosions[i]);
                world.Explosions.RemoveAt(i);
            }

            spriteBatch.Begin();

            world.Draw(spriteBatch, camera.Position);

            // For each person in the session...
            foreach (NetworkGamer gamer in mNetworkSession.AllGamers())
            {
                // Look up the tank object belonging to this network gamer.
                mPlayer player = gamer.Tag as mPlayer;

                if (player.tank.IsAlive)
                {
                    player.CameraPosition = camera.Position;
                    player.tank.SetPositionToCircularWorld();
                    player.tank.Draw(spriteBatch, mStaticPlayer.TeamColors[player.team], gamer.Gamertag);
                    player.weapon.Draw(spriteBatch);
                }
            }

            spriteBatch.End();
            DrawVictoryStatus();
        }

        #endregion

        #region Handle Input

        /// <summary>
        /// Helper for updating a locally controlled gamer.
        /// </summary>
        void UpdateLocalGamer(LocalNetworkGamer gamer)
        {
            // Look up what tank is associated with this local player,
            // and read the latest user inputs for it. The server will
            // later use these values to control the tank movement.
            mPlayer localPlayer = gamer.Tag as mPlayer;

            ReadPlayerInputs(localPlayer, gamer.SignedInGamer.PlayerIndex);

            mNetworkSession.UpdateLocalGamer(gamer);
        }

        /// <summary>
        /// Handles input.
        /// </summary>
        private void HandleInput()
        {
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);
            currentMouseState = Mouse.GetState();

            mousePosition.X = currentMouseState.X;
            mousePosition.Y = currentMouseState.Y;

            // Check for exit.
            if (IsActive && IsPressed(Keys.Escape, Buttons.Back))
            {
                Exit();
            }
        }

        /// <summary>
        /// Checks if the specified button is pressed on either keyboard or gamepad.
        /// </summary>
        bool IsPressed(Keys key, Buttons button)
        {
            return (currentKeyboardState.IsKeyDown(key) ||
                    currentGamePadState.IsButtonDown(button));
        }

        /// <summary>
        /// Reads input data from keyboard, and stores
        /// it into the specified mPlayer object.
        /// </summary>
        void ReadPlayerInputs(mPlayer player, PlayerIndex playerIndex)
        {
            // Read the keyboard.
            KeyboardState keyboard = Keyboard.GetState(playerIndex);
            MouseState mouse = Mouse.GetState();

            Vector2 mouseDir = new Vector2(mouse.X, mouse.Y);
            mouseDir -= mStaticWorld.ScreenCenter;

            float angle = (float)Math.Atan2(Math.Abs(mouseDir.Y), Math.Abs(mouseDir.X));
            if (mouseDir.X < 0 && mouseDir.Y > 0)
            {
                angle = (float)Math.PI - angle;
            }
            else if (mouseDir.X <= 0 && mouseDir.Y <= 0)
            {
                angle = (float)Math.PI + angle;
            }
            else if (mouseDir.X > 0 && mouseDir.Y < 0)
            {
                angle = 2 * (float)Math.PI - angle;
            }
            float angleBack = angle + (float)Math.PI;
            if (angleBack > (float)Math.PI) angleBack -= (float)Math.PI;

            float diffAngle = angle - player.tank.TurretRotation;
            int diffQuadrant = Quadrant(angle) - Quadrant(player.tank.TurretRotation);

            if (diffQuadrant == 0)
            {
                if (diffAngle > 0)
                {
                    if (diffAngle < 2 * player.tank.TurretRotationRate)
                        player.Turret(mStaticPlayer.DirectionsTurret.ClockWiseSmall);
                    else
                        player.Turret(mStaticPlayer.DirectionsTurret.ClockWise);
                }
                else if (diffAngle < 0)
                {
                    if (Math.Abs(diffAngle) < 2 * player.tank.TurretRotationRate)
                        player.Turret(mStaticPlayer.DirectionsTurret.AntiClockWiseSmall);
                    else
                        player.Turret(mStaticPlayer.DirectionsTurret.AntiClockWise);
                }
                else
                    player.Turret(mStaticPlayer.DirectionsTurret.None);
            }
            else if (Math.Abs(diffQuadrant) == 1 || Math.Abs(diffQuadrant) == 3)
            {
                if (diffQuadrant == 1 || diffQuadrant == -3)
                {
                    player.Turret(mStaticPlayer.DirectionsTurret.ClockWise);
                }
                else
                {
                    player.Turret(mStaticPlayer.DirectionsTurret.AntiClockWise);
                }
            }
            else
            {
                if (angleBack < player.tank.TurretRotation)
                {
                    player.Turret(mStaticPlayer.DirectionsTurret.ClockWise);
                }
                else
                {
                    player.Turret(mStaticPlayer.DirectionsTurret.AntiClockWise);
                }
            }

            if (keyboard.IsKeyDown(Keys.A))
            {
                player.Doblar(mStaticPlayer.DirectionsLateral.Left);
            }
            else if (keyboard.IsKeyDown(Keys.D))
            {
                player.Doblar(mStaticPlayer.DirectionsLateral.Right);
            }
            else
            {
                player.Doblar(mStaticPlayer.DirectionsLateral.None);
            }

            if (keyboard.IsKeyDown(Keys.W))
            {
                player.Advance(mStaticPlayer.DirectionsFrontal.Forward);
            }
            else if (keyboard.IsKeyDown(Keys.S))
            {
                player.Advance(mStaticPlayer.DirectionsFrontal.Backward);
            }
            else
            {
                player.Advance(mStaticPlayer.DirectionsFrontal.None);
            }

            if (mouse.LeftButton == ButtonState.Pressed)
            {
                player.Action(mStaticPlayer.Action.Shoot);
            }
            else
            {
                player.Action(mStaticPlayer.Action.None);
            }

        }

        int Quadrant(float angle)
        {
            if (0 <= angle && angle < (float)Math.PI / 2) return 1;
            if ((float)Math.PI / 2 <= angle && angle < (float)Math.PI) return 2;
            if ((float)Math.PI <= angle && angle < 3 * (float)Math.PI / 2) return 3;
            if (3 * (float)Math.PI / 2 <= angle && angle < 2 * (float)Math.PI) return 4;
            return 0;
        }

        #endregion
    
    }
}
